1 module hip.windowing.platforms.x11;
2 
3 version(X11):
4 
5 import core.stdc.stdio;
6 
7 import hip.windowing.platforms.x11lib.glx;
8 import hip.windowing.platforms.x11lib.x11;
9 import hip.windowing.events;
10 import hip.windowing.input;
11 
12 public import hip.windowing.platforms.x11lib.x11;
13 
14 package struct X11WindowData
15 {
16     Display* display;
17     Window window;
18     Screen* screen;
19     XVisualInfo* visual;
20     int screenId;
21     int width, height;
22     Colormap colormap;
23     GLXContext glContext;
24     Atom atomWmDeleteWindow;
25 
26 }
27 package X11WindowData x11win;
28 
29 package __gshared bool ctxErrorOccurred = false;
30 
31 package extern(C) nothrow @system @nogc
32 int ctxErrorHandler( Display *dpy, XErrorEvent *ev )
33 {
34     ctxErrorOccurred = true;
35     return 0;
36 }
37 
38 
39 nothrow @nogc bool initializeOpenGL(int majorVersion, int minorVersion, void* WindowHandle)
40 {
41     GLint[23] glxAttribs = [
42         GLX_X_RENDERABLE    , True,
43 		GLX_DRAWABLE_TYPE   , GLX_WINDOW_BIT,
44 		GLX_RENDER_TYPE     , GLX_RGBA_BIT,
45 		GLX_X_VISUAL_TYPE   , GLX_TRUE_COLOR,
46 		GLX_RED_SIZE        , 8,
47 		GLX_GREEN_SIZE      , 8,
48 		GLX_BLUE_SIZE       , 8,
49 		GLX_ALPHA_SIZE      , 8,
50 		GLX_DEPTH_SIZE      , 24,
51 		GLX_STENCIL_SIZE    , 8,
52 		GLX_DOUBLEBUFFER    , True,
53 		None
54     ];
55     
56     int fbcount;
57     GLXFBConfig* fbc = glXChooseFBConfig(x11win.display, x11win.screenId, glxAttribs.ptr, &fbcount);
58     if (fbc is null || fbcount == 0) 
59     {
60         printf("Failed to retrieve framebuffer.\n");
61         XCloseDisplay(x11win.display);
62         return 1;
63     }
64 
65     // Pick the FB config/visual with the most samples per pixel
66 	int best_fbc = -1, worst_fbc = -1, best_num_samp = -1, worst_num_samp = 999;
67 	for (int i = 0; i < fbcount; ++i) {
68 		XVisualInfo *vi = glXGetVisualFromFBConfig( x11win.display, fbc[i] );
69 		if ( vi != null) {
70 			int samp_buf, samples;
71 			glXGetFBConfigAttrib( x11win.display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf );
72 			glXGetFBConfigAttrib( x11win.display, fbc[i], GLX_SAMPLES       , &samples  );
73 
74 			if ( best_fbc < 0 || (samp_buf && samples > best_num_samp) ) {
75 				best_fbc = i;
76 				best_num_samp = samples;
77 			}
78 			if ( worst_fbc < 0 || !samp_buf || samples < worst_num_samp )
79 				worst_fbc = i;
80 			worst_num_samp = samples;
81 		}
82 		XFree( vi );
83 	}
84 
85 
86     //Some would try to get the best framebuffer, but, is that really necessary? Get the first
87     GLXFBConfig bestFbc = fbc[best_fbc];
88     XFree(fbc);
89     x11win.visual = glXGetVisualFromFBConfig(x11win.display, bestFbc);
90     if(x11win.visual == null)
91     {
92         printf("Could not create correct visual window \n");
93         XCloseDisplay(x11win.display);
94         return false;
95     }
96 
97     //Open the window
98     XSetWindowAttributes windowAttribs;
99     windowAttribs.border_pixel = BlackPixel(x11win.display, x11win.screenId);
100     windowAttribs.background_pixel = WhitePixel(x11win.display, x11win.screenId);
101     windowAttribs.override_redirect = True;
102     windowAttribs.colormap = XCreateColormap(
103         x11win.display, RootWindow(x11win.display, x11win.screenId), x11win.visual.visual, AllocNone
104     );
105 
106     windowAttribs.event_mask = PointerMotionMask |
107                              ButtonPressMask |
108                              ButtonReleaseMask |
109 	    		     KeyPressMask |
110 	    		     KeyReleaseMask |
111                              EnterWindowMask |
112                              LeaveWindowMask |
113                              ExposureMask;
114 
115 
116 
117     x11win.window = XCreateWindow(
118         x11win.display, 
119         RootWindow(x11win.display, x11win.screenId), 
120         0, 0, x11win.width, x11win.height,
121         0, x11win.visual.depth, InputOutput, x11win.visual.visual,
122         CWBackPixel | CWColormap | CWBorderPixel | CWEventMask, 
123         &windowAttribs
124     );
125     if(!x11win.window)
126     {
127         printf("Could not create XWindow\n");
128         return false;
129     }
130 
131     Atom atomWmDeleteWindow = XInternAtom(x11win.display, "WM_DELETE_WINDOW", False);
132     if(atomWmDeleteWindow == BadAlloc)
133     {
134         printf("X Server failed to allocate WM_DELETE_WINDOW\n");
135         return false;
136     }
137     else if(atomWmDeleteWindow == BadValue)
138     {
139         printf("WM_DELETE_WINDOW is not a valid argument\n");
140         return false;
141     }
142     x11win.atomWmDeleteWindow = atomWmDeleteWindow;
143     Status st = XSetWMProtocols(x11win.display, x11win.window, &atomWmDeleteWindow, 1);
144     if(st == BadAlloc)
145     {
146         printf("XServer failed to allocate resources for SetWMProtocols\n");
147         return false;
148     }
149     else if(st == BadWindow)
150     {
151         printf("The window argument does not name a defined window\n");
152         return false;
153     }
154 
155     const(char)* glExts = glXQueryExtensionsString(x11win.display, DefaultScreen(x11win.display));
156 
157     glXCreateContextAttribsARBProc glXCreateContextAttribsARB;
158     glXCreateContextAttribsARB = cast(glXCreateContextAttribsARBProc)glXGetProcAddressARB(cast(const(GLubyte)*)"glXCreateContextAttribsARB");
159 
160     GLXContext glContext;
161 
162 
163     if(!isExtensionSupported(glExts, "GLX_ARB_create_context") || glXCreateContextAttribsARB is null)
164     {
165         printf("glXCreateContextAttribsARB() not found, using old style GLX context");
166         x11win.glContext = glContext = glXCreateNewContext(x11win.display, bestFbc, GLX_RGBA_TYPE, null, True);
167     }
168     else
169     {
170         int[7] context_attribs =
171         [
172             GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
173             GLX_CONTEXT_MINOR_VERSION_ARB, 3, //3.3 is the minimum here
174             GLX_CONTEXT_FLAGS_ARB        , GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
175             None
176         ];
177 
178         x11win.glContext = glContext = glXCreateContextAttribsARB(x11win.display, bestFbc, null,
179                                       True, context_attribs.ptr );
180     }
181     if(x11win.glContext == null)
182     {
183         printf("Could not create GLX Context\n");
184         return false;
185     }
186     XSync(x11win.display, False);
187 
188     if (!glXIsDirect (x11win.display, x11win.glContext)) 
189 		printf("Indirect GLX rendering context obtained\n");
190     if(glXMakeCurrent(x11win.display, x11win.window, x11win.glContext) == 0)
191     {
192         printf("Could not make GLX Context as current\n");
193         return false;
194     }
195     //Show Window
196     XClearWindow(x11win.display, x11win.window);
197     XMapRaised(x11win.display, x11win.window);
198     XStoreName(x11win.display, x11win.window, "HipremeEngine");
199 
200     string[] errors;
201     setVsyncActive(false, null, errors);
202 
203 
204     return true;
205 }
206 
207 void show(void* WindowHandle){}
208 void swapBuffer()
209 {
210     glXSwapBuffers(x11win.display, x11win.window);
211 }
212 
213 void setVsyncActive(bool active, void* WindowHandle, ref string[] errors) @nogc nothrow @system
214 {
215     static bool loadedSymbols = false;
216     if(!loadedSymbols)
217     {
218         glXSwapIntervalEXT = cast(glXSwapIntervalEXTProc)glXGetProcAddressARB(cast(GLubyte*)"glXSwapIntervalEXT");
219         glXSwapIntervalMESA = cast(glXSwapIntervalMESAProc)glXGetProcAddressARB(cast(GLubyte*)"glXSwapIntervalMESA");
220         glXSwapIntervalSGI = cast(glXSwapIntervalSGIProc)glXGetProcAddressARB(cast(GLubyte*)"glXSwapIntervalSGI");
221         loadedSymbols = true;
222     }
223     glXSwapIntervalEXT(x11win.display, x11win.window, cast(int)active);
224     glXSwapIntervalMESA(cast(int)active);
225     glXSwapIntervalSGI(cast(int)active);
226 }
227 
228 void setWindowName(string name, void* WindowHandle, ref string[] errors)
229 {
230     XStoreName(x11win.display, x11win.window, name.ptr);
231 }
232 
233 
234 pragma(inline) wchar convertKeycodeToScancode(XKeyEvent* ev)
235 {
236     char[2] buffer = '\0';
237     KeySym ks;
238     int allocated = XLookupString(ev, buffer.ptr, buffer.length, &ks, null);
239     if(allocated > 1)
240         printf("%*s", cast(int)buffer.length, buffer.ptr);
241     return *cast(wchar*)(cast(void*)buffer.ptr);
242 }
243 
244 void poll()
245 {
246     XEvent ev;
247     while (XPending(x11win.display) > 0) 
248     {
249         XNextEvent(x11win.display, &ev);
250         switch(ev.type)
251         {
252             case Expose:
253             {
254                 XWindowAttributes attribs;
255                 XGetWindowAttributes(x11win.display, x11win.window, &attribs);
256                 printf("Expose event\n");
257                 break;
258             }
259             case ClientMessage:
260                 if(ev.xclient.data.l[0] == x11win.atomWmDeleteWindow && onWindowClosed != null)
261                     onWindowClosed();
262                 break;
263             case DestroyNotify:
264             {
265                 if(onWindowClosed != null)
266                     onWindowClosed();
267                 break;
268             }
269             case ButtonPress:
270             {
271                 int x = ev.xbutton.x;
272                 int y = ev.xbutton.y;
273                 switch(ev.xbutton.button)
274                 {
275                     case 1: //Left
276                         if(onMouseDown != null)
277                             onMouseDown(HipWindowingMouseButton.left, x, y);
278                         break;
279                     case 2: //Middle
280                         if(onMouseDown != null)
281                             onMouseDown(HipWindowingMouseButton.middle, x, y);
282                         break;
283                     case 3: //Right
284                         if(onMouseDown != null)
285                             onMouseDown(HipWindowingMouseButton.right, x, y);
286                         break;
287                     case 4: //Scroll up
288                         if(onMouseWheel != null)
289                             onMouseWheel(0, -1);
290                         break;
291                     case 5: //Scroll down
292                         if(onMouseWheel != null)
293                             onMouseWheel(0, 1);
294                         break;
295                     default: break;
296                 }
297             } break;
298             case ButtonRelease:
299             {
300                 int x = ev.xbutton.x;
301                 int y = ev.xbutton.y;
302                 switch(ev.xbutton.button)
303                 {
304                     case 1: //Left
305                         if(onMouseUp != null)
306                             onMouseUp(HipWindowingMouseButton.left, x, y);
307                         break;
308                     case 2: //Middle
309                         if(onMouseUp != null)
310                             onMouseUp(HipWindowingMouseButton.middle, x, y);
311                         break;
312                     case 3: //Right
313                         if(onMouseUp != null)
314                             onMouseUp(HipWindowingMouseButton.right, x, y);
315                         break;
316                     default: break;
317                 }
318             } break;
319             case MotionNotify:
320                 if(onMouseMove != null)
321                     onMouseMove(ev.xmotion.x, ev.xmotion.y);
322                 break;
323             case KeyPress:
324                 if(onKeyDown != null)
325                     onKeyDown(cast(uint)XKeycodeToKeysym(x11win.display, ev.xkey.keycode, ev.xkey.state & ShiftMask ? 1 : 0));
326                     // onKeyDown(convertKeycodeToScancode(&ev.xkey));
327                 break;
328             case KeyRelease:
329                 if(onKeyUp != null)
330                     onKeyUp(cast(uint)XKeycodeToKeysym(x11win.display, ev.xkey.keycode, ev.xkey.state & ShiftMask ? 1 : 0));
331                     // onKeyUp(convertKeycodeToScancode(&ev.xkey));
332                 break;
333             default:break;
334         }
335     }
336 }
337 
338 ///Returns [width, height]
339 int[2] getWindowSize(void* WindowHandle, ref string[] errors) @nogc
340 {
341     XWindowAttributes att;
342     XGetWindowAttributes(x11win.display, x11win.window, &att);
343     return [att.width, att.height];
344 }
345 
346 void setWindowSize(int width, int height, void* WindowHandle, ref string[] errors) @nogc
347 {
348     uint change_values = CWWidth | CWHeight;
349     XWindowChanges values;
350     values.width = width;
351     values.height = height;
352     XConfigureWindow(x11win.display, x11win.window, change_values, &values);
353 }
354 import hip.windowing.platforms.null_;
355 alias setFullscreen = hip.windowing.platforms.null_.setFullscreen;
356 
357 bool destroy_GL_Context() @nogc
358 {
359     XDestroyWindow(x11win.display, x11win.window);
360     XCloseDisplay(x11win.display);
361     XFree(x11win.visual);
362     XFreeColormap(x11win.display, x11win.colormap);
363     glXDestroyContext(x11win.display, x11win.glContext);
364     return true;
365 }
366 
367 int openWindow(int width, int height, out void* WindowHandle) @nogc
368 {
369     x11win.width = width;
370     x11win.height = height;
371     //Open the display
372     x11win.display = XOpenDisplay(null);
373     if(x11win.display == null)
374     {
375         printf("Could not open display.\n");
376         return 1;
377     }
378     int glx_major, glx_minor;
379     if ( !glXQueryVersion(x11win.display, &glx_major, &glx_minor ) || 
380        ( ( glx_major == 1 ) && ( glx_minor < 3 ) ) || ( glx_major < 1 ) )
381     {
382         printf("Invalid GLX version\n");
383         return 1;
384     }
385     x11win.screenId = DefaultScreen(x11win.display);
386     
387     return 0;
388 }